package io.gitee.tooleek.lock.spring.boot.core;

import io.gitee.tooleek.lock.spring.boot.exception.LockTimeoutException;
import io.gitee.tooleek.lock.spring.boot.util.MD5Utils;
import org.apache.zookeeper.common.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import io.gitee.tooleek.lock.spring.boot.annotation.Key;
import io.gitee.tooleek.lock.spring.boot.annotation.Lock;
import io.gitee.tooleek.lock.spring.boot.annotation.RLock;
import io.gitee.tooleek.lock.spring.boot.annotation.ZLock;
import io.gitee.tooleek.lock.spring.boot.core.key.KeyStrategyContext;
import io.gitee.tooleek.lock.spring.boot.core.key.LockKey;
import io.gitee.tooleek.lock.spring.boot.core.key.LockKey.Builder;
import io.gitee.tooleek.lock.spring.boot.core.key.RedisLockKey;
import io.gitee.tooleek.lock.spring.boot.core.key.RedisLockKey.RedisKeyBuilder;
import io.gitee.tooleek.lock.spring.boot.core.key.ZookeeperLockKey;
import io.gitee.tooleek.lock.spring.boot.core.key.ZookeeperLockKey.ZookeeperKeyBuilder;
import io.gitee.tooleek.lock.spring.boot.core.key.strategy.ClassKeyStrategy;
import io.gitee.tooleek.lock.spring.boot.core.key.strategy.KeyStrategy;
import io.gitee.tooleek.lock.spring.boot.core.key.strategy.MethodKeyStrategy;
import io.gitee.tooleek.lock.spring.boot.core.key.strategy.ParameterKeyStrategy;
import io.gitee.tooleek.lock.spring.boot.core.key.strategy.PropertiesKeyStrategy;
import io.gitee.tooleek.lock.spring.boot.enumeration.LockScheme;
import io.gitee.tooleek.lock.spring.boot.factory.FactoryBean;
import io.gitee.tooleek.lock.spring.boot.service.LockService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * 锁拦截器
 *
 * @author TanRq
 */
@Aspect
@Component
public class LockInterceptor {

    Logger logger = LoggerFactory.getLogger(LockInterceptor.class);

    @Autowired
    private FactoryBean factoryBean;

    private ThreadLocal<Map<String, LockService>> localLockServiceMap = new ThreadLocal<>();

    /**
     * 获取LockService的Key,用于存放在localLockServiceMap中
     *
     * @param className  类名
     * @param methodName 方法名
     * @return
     */
    private String getLockServiceKey(String className, String methodName, LockKey lockKey) {
        //重写获取lockkey方法，将生成的key一起放入作为判断条件，具体化key，防止lockkey被覆盖
        String[] fields = className.split("\\.");
        String simpleClassName = fields[fields.length - 1];
        String keyStr = StringUtils.joinStrings(lockKey.getKeyList(), ";");
        String lockStr = simpleClassName + "." + methodName + ":" + keyStr;
        return MD5Utils.getMD5String(lockStr);
    }

    @Around(value = "@annotation(io.gitee.tooleek.lock.spring.boot.annotation.Lock)||@annotation(io.gitee.tooleek.lock.spring.boot.annotation.RLock)||@annotation(io.gitee.tooleek.lock.spring.boot.annotation.ZLock)")
    public Object lockHandle(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = methodSignature.getMethod();
        Method realMethod = joinPoint.getTarget().getClass().getDeclaredMethod(methodSignature.getName(),
                targetMethod.getParameterTypes());

        LockScheme scheme = getLockScheme(realMethod);

        Object[] args = joinPoint.getArgs();

        String className = joinPoint.getTarget().getClass().getName();
        String methodName = methodSignature.getName();

        KeyStrategy keyStrategy = getKeyStrategy(scheme, className, methodName, realMethod, args);

        Builder keyBuilder = new KeyStrategyContext(keyStrategy).generateBuilder();

        LockKey lockKey = null;
        if (keyBuilder instanceof RedisKeyBuilder) {
            lockKey = buildRedisLockKey(realMethod, (RedisKeyBuilder) keyBuilder);
        }
        if (keyBuilder instanceof ZookeeperKeyBuilder) {
            lockKey = buildZookeeperLockKey(realMethod, (ZookeeperKeyBuilder) keyBuilder);
        }

        LockService lockService = getLockService(scheme, realMethod);
        lockService.setLockKey(lockKey);

        String lockServiceKey = getLockServiceKey(className, methodName, lockKey);
        Map<String, LockService> lockServiceMap = localLockServiceMap.get();
        if (lockServiceMap == null) {
            lockServiceMap = new HashMap<>();
            localLockServiceMap.set(lockServiceMap);
        }

        lockService.lock();

        lockServiceMap.put(lockServiceKey, lockService);

        logger.debug("============================================================");
        logger.debug("===> 执行线程：[{}]  加锁任务：开始", Thread.currentThread().getName());
        logger.debug("===> 执行线程：[{}]  执行操作：加锁", Thread.currentThread().getName());
        logger.debug("===> 执行线程：[{}]  加锁方法：{}", Thread.currentThread().getName(), lockServiceKey);

        return joinPoint.proceed();
    }

    private LockService getLockService(LockScheme scheme, Method realMethod) {
        if (scheme == LockScheme.REDIS) {
            if (null != realMethod.getAnnotation(Lock.class)) {
                Lock lock = realMethod.getAnnotation(Lock.class);
                return factoryBean.getFactory(scheme).getService(lock.lockType());
            }
            if (null != realMethod.getAnnotation(RLock.class)) {
                RLock lock = realMethod.getAnnotation(RLock.class);
                return factoryBean.getFactory(scheme).getService(lock.lockType());
            }
        }
        if (scheme == LockScheme.ZOOKEEPER) {
            ZLock zlock = realMethod.getAnnotation(ZLock.class);
            return factoryBean.getFactory(scheme).getService(zlock.interProcess());
        }
        return null;
    }

    private LockScheme getLockScheme(Method realMethod) {
        if (null != realMethod.getAnnotation(Lock.class) || null != realMethod.getAnnotation(RLock.class)) {
            return LockScheme.REDIS;
        }
        return LockScheme.ZOOKEEPER;
    }

    private ZookeeperLockKey buildZookeeperLockKey(Method realMethod, ZookeeperKeyBuilder keyBuilder) {
        ZLock zlook = realMethod.getAnnotation(ZLock.class);
        return keyBuilder.interProcess(zlook.interProcess()).waitTime(zlook.waitTime()).timeUnit(zlook.timeUnit()).build();
    }

    private RedisLockKey buildRedisLockKey(Method realMethod, RedisKeyBuilder keyBuilder) {
        Lock lock = realMethod.getAnnotation(Lock.class);
        if (null != lock) {
            return keyBuilder.leaseTime(lock.leaseTime()).waitTime(lock.waitTime()).timeUnit(lock.timeUnit()).build();
        }
        RLock rlock = realMethod.getAnnotation(RLock.class);
        return keyBuilder.leaseTime(rlock.leaseTime()).waitTime(rlock.waitTime()).timeUnit(rlock.timeUnit()).build();
    }

    private KeyStrategy getKeyStrategy(LockScheme scheme, String className, String methodName, Method realMethod,
                                       Object[] args) {
        // 参数锁
        for (int i = 0; i < realMethod.getParameters().length; i++) {
            if (realMethod.getParameters()[i].isAnnotationPresent(Key.class)) {
                return new ParameterKeyStrategy(scheme, className, methodName, realMethod, args);
            }
        }
        // 方法锁
        if (null != realMethod.getAnnotation(Key.class)) {
            return new MethodKeyStrategy(scheme, className, methodName, realMethod, args);
        }
        // 属性锁
        for (int i = 0; i < args.length; i++) {
            Object obj = args[i];
            @SuppressWarnings("rawtypes")
            Class objectClass = obj.getClass();
            Field[] fields = objectClass.getDeclaredFields();
            for (Field field : fields) {
                if (null != field.getAnnotation(Key.class)) {
                    return new PropertiesKeyStrategy(scheme, className, methodName, realMethod, args);
                }
            }
        }
        // 类名和方法名作为key的锁
        return new ClassKeyStrategy(scheme, className, methodName, realMethod, args);
    }

    @AfterReturning(value = "@annotation(io.gitee.tooleek.lock.spring.boot.annotation.Lock)||@annotation(io.gitee.tooleek.lock.spring.boot.annotation.RLock)||@annotation(io.gitee.tooleek.lock.spring.boot.annotation.ZLock)")
    public void afterReturning(JoinPoint joinPoint) {
        release(joinPoint);
    }

    @AfterThrowing(throwing = "ex", value = "@annotation(io.gitee.tooleek.lock.spring.boot.annotation.Lock)||@annotation(io.gitee.tooleek.lock.spring.boot.annotation.RLock)||@annotation(io.gitee.tooleek.lock.spring.boot.annotation.ZLock)")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
        if (ex instanceof LockTimeoutException) {
            return;
        }
        release(joinPoint);
    }

    /**
     * 释放 锁和LockService
     *
     * @param joinPoint
     */
    private void release(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = methodSignature.getMethod();
        Method realMethod = null;
        try {
            realMethod = joinPoint.getTarget().getClass().getDeclaredMethod(methodSignature.getName(),
                    targetMethod.getParameterTypes());
        } catch (NoSuchMethodException e) {
            logger.error(e.getMessage());
            return;
        }

        LockScheme scheme = getLockScheme(realMethod);

        Object[] args = joinPoint.getArgs();

        String className = joinPoint.getTarget().getClass().getName();
        String methodName = methodSignature.getName();

        KeyStrategy keyStrategy = getKeyStrategy(scheme, className, methodName, realMethod, args);

        Builder keyBuilder = new KeyStrategyContext(keyStrategy).generateBuilder();

        LockKey lockKey = null;
        if (keyBuilder instanceof RedisKeyBuilder) {
            lockKey = buildRedisLockKey(realMethod, (RedisKeyBuilder) keyBuilder);
        }
        if (keyBuilder instanceof ZookeeperKeyBuilder) {
            lockKey = buildZookeeperLockKey(realMethod, (ZookeeperKeyBuilder) keyBuilder);
        }

        String lockServiceKey = getLockServiceKey(className, methodName, lockKey);
        Map<String, LockService> lockServiceMap = localLockServiceMap.get();

        logger.debug("===> 执行线程：[{}]  执行操作：解锁", Thread.currentThread().getName());
        logger.debug("===> 执行线程：[{}]  解锁方法：{}", Thread.currentThread().getName(), lockServiceKey);
        LockService lockService = lockServiceMap.get(lockServiceKey);
        if (lockService != null) {
            lockService.release();
        }

        logger.debug("===> 执行线程：[{}]  释放资源：Service", Thread.currentThread().getName());
        lockServiceMap.remove(lockServiceKey);

        if (lockServiceMap.isEmpty()) {
            logger.debug("===> 执行线程：[{}]  释放资源：ServiceMap", Thread.currentThread().getName());
            localLockServiceMap.remove();
        }

        logger.debug("===> 执行线程：[{}]  加锁任务：结束", Thread.currentThread().getName());
        logger.debug("============================================================");
        // 原始方法
        // localLockService.get().release();
        // localLockService.remove();
    }

}
